package com.hqd.ch03.v25.aop.framework;

import com.hqd.ch03.v25.aop.Advised;
import com.hqd.ch03.v25.aopalliance.intercept.MethodInvocation;
import com.hqd.ch03.v25.core.DecoratingProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class JdkDynamicAopProxy implements InvocationHandler, AopProxy {
    private final AdvisedSupport advised;

    private final Class<?>[] proxiedInterfaces;


    public JdkDynamicAopProxy(AdvisedSupport config) {
        this.advised = config;
        this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    }

    @Override
    public Object getProxy() {
        return getProxy(Thread.currentThread().getContextClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;

        Object target = this.advised.getTarget();
        try {
            if (method.getDeclaringClass() == DecoratingProxy.class) {
                return AopProxyUtils.ultimateTargetClass(this.advised);
            } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                    method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                return method.invoke(this.advised, args);
            }

            Object retVal;

            /**
             * 代理对象可见，绑定到线程中去
             */
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }


            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());

            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = method.invoke(target, argsToUse);
            } else {
                MethodInvocation invocation =
                        new ReflectiveMethodInvocation(target, proxy, method, args, chain.toArray());
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            /**
             * 返回值是否是this，如果是this，则判断是否需要返回代理对象
             */
            if (retVal != null && retVal == target &&
                    returnType != Object.class && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new RuntimeException(
                        "Null return value from advice does not match primitive return type for: " + method);
            }
            return retVal;
        } finally {
            if (setProxyContext) {
                this.advised.getTargetSource().releaseTarget(oldProxy);
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
}
